/*******************************************************************************
 * Copyright (c) 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.pde.internal.core.product;

import java.io.*;
import java.text.MessageFormat;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.*;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jface.text.*;
import org.eclipse.pde.core.plugin.IPluginBase;
import org.eclipse.pde.core.plugin.IPluginImport;
import org.eclipse.pde.internal.core.ibundle.IBundlePluginModel;
import org.eclipse.pde.internal.core.iproduct.IProductCustomizationConfigurer;
import org.eclipse.pde.internal.core.iproduct.IProductCustomizationConstants;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;

/**
 * 
 */
public class ProductCustomizationConfigurer implements IProductCustomizationConfigurer {

	/**
	 * name of registration object
	 */
	private static final String REGISTRATION_NAME = "fRegistration";

	private IBundlePluginModel bundleModel;

	/**
	 * @param bundleModel
	 */
	public ProductCustomizationConfigurer(IBundlePluginModel bundleModel) {
		this.bundleModel = bundleModel;
	}

	/*
	 * (non-Javadoc)
     *
	 * @see org.eclipse.pde.internal.core.iproduct.IProductCustomizationConfigurer#configureBundle()
	 */
	public void configureBundle(IProgressMonitor monitor) throws CoreException {
		configureDependencies(monitor);
		configureActivator(monitor);
	}

	private void configureDependencies(IProgressMonitor monitor) throws CoreException {
		addDependency("system.bundle");
		addDependency("org.eclipse.equinox.transforms.xslt");
		bundleModel.save();
	}

	/**
	 * @param targetId
	 * @throws CoreException
	 */
	private void addDependency(String targetId) throws CoreException {
		IPluginImport[] imports = bundleModel.getPluginBase().getImports();
		for (int i = 0; i < imports.length; i++) {
			IPluginImport pluginImport = imports[i];
			if (pluginImport.getId().equals(targetId))
				return; // we have found it
		}

		IPluginBase model = bundleModel.createPluginBase();
		IPluginImport importNode = bundleModel.createImport();
		importNode.setId(targetId);
		model.add(importNode);
	}

	private void configureActivator(IProgressMonitor monitor, IType activatorType, String methodName, String methodBody) throws CoreException {

		String error = MessageFormat.format("Unable to parse {0}(BundleContext) in activator for transform bundle.", new Object[] {methodName});
		IMethod method = findMethod(activatorType, methodName, "org.eclipse.osgi", "BundleContext");
		if (method == null || !method.exists()) {
			String methodSignature = MessageFormat.format("public void {0} (BundleContext context) throws Exception {{", new Object[] {methodName});
			activatorType.getCompilationUnit().createImport("org.osgi.framework.BundleContext", null, monitor);
			StringBuffer contents = new StringBuffer();
			contents.append(methodSignature);
			contents.append('\n');
			contents.append(methodBody);
			contents.append("}");
			activatorType.createMethod(contents.toString(), null, false, monitor);
		} else {
			String existingSource = method.getSource();
			StringBuffer buffer = new StringBuffer();
			BufferedReader reader = new BufferedReader(new StringReader(existingSource));
			String line = null;
			boolean foundStartToken = false;
			try {
				while ((line = reader.readLine()) != null) {
					if (line.indexOf(IProductCustomizationConstants.PDE_AUTOGEN_START) == -1)
						buffer.append(line).append('\n');
					else {
						foundStartToken = true;
						break;
					}
				}
				if (foundStartToken) // good news - we found our token
					buffer.append(methodBody);
				else { // we have a virgin method here. We've exhausted the
					// buffer without finding our token. This means we've
					// actually closed the method body. We need to go back
					// and find the right spot.

					int insertIndex = buffer.length();
					while (insertIndex >= 0) {
						insertIndex--;
						if (buffer.charAt(insertIndex) == '}') {
							insertIndex--;
							break;
						}
					}
					if (insertIndex < 0) // we didn't find a brace either?
						createCoreException(error);
					else
						buffer.insert(insertIndex, methodBody);
				}
				boolean foundEndToken = false;

				while ((line = reader.readLine()) != null) {
					if (line.indexOf(IProductCustomizationConstants.PDE_AUTOGEN_END) != -1)
						foundEndToken = true;
					else if (foundEndToken)
						buffer.append(line).append('\n');
				}
				IDocument document = new Document(buffer.toString());
				TextEdit methodText = ToolFactory.createCodeFormatter(JavaCore.getDefaultOptions()).format(CodeFormatter.K_UNKNOWN, buffer.toString(), 0, buffer.length(), 0, null);
				try {
					if (methodText != null) methodText.apply(document);
				} catch (MalformedTreeException e) {
				} catch (BadLocationException e) {
				}

				method.delete(true, monitor);
				method = activatorType.createMethod(document.get(), null, true, monitor);
			} catch (IOException e) {
				throw createCoreException(error);

			}
		}
	}

	/**
	 * @param monitor
	 * @throws CoreException
	 * @throws JavaModelException
	 */
	private void configureActivator(IProgressMonitor monitor) 
			throws CoreException, JavaModelException {
		IProject project = bundleModel.getUnderlyingResource().getProject();
		String activatorName = bundleModel.getPlugin().getClassName();
		if (activatorName == null) {
			// TODO create the activator in this (rarish) case
			throw createCoreException("Transforms cannot be applied to a bundle that does not have an activator.");
		}

		IJavaProject jProject = JavaCore.create(project);
		IType activatorType = jProject.findType(activatorName);
		ICompilationUnit compilationUnit = activatorType.getCompilationUnit();
		compilationUnit.becomeWorkingCopy(monitor);
		compilationUnit.getType(activatorName);
		
		IField field = activatorType.getField(REGISTRATION_NAME);
		compilationUnit.createImport("java.net.URL", null, monitor);
		compilationUnit.createImport("java.util.Properties", null, monitor);
		compilationUnit.createImport("org.osgi.framework.ServiceRegistration", null, monitor);
		if (field == null || !field.exists()) {

			field = activatorType.createField("private ServiceRegistration " + REGISTRATION_NAME + ";", null, false, monitor);
		}
		configureActivator(monitor, activatorType, "start", getStartCode());

		configureActivator(monitor, activatorType, "stop", getStopCode());

		compilationUnit.commitWorkingCopy(true, monitor);
	}

	private static String getStartCode() {
		StringBuffer buf = new StringBuffer();
		buf.append("//").append(IProductCustomizationConstants.PDE_AUTOGEN_START).append(" - do not delete\n");
		buf.append("Properties properties = new Properties();\n");
		buf.append("properties.put(\"equinox.transformerType\",");
		buf.append("\"").append(IProductCustomizationConstants.TYPE_XSLT).append("\")");
		buf.append("; //$NON-NLS-1$ //$NON-NLS-2$\n");
		buf.append(REGISTRATION_NAME).append(" = context.registerService(URL.class.getName(), context.getBundle().getEntry(\"");
		buf.append(IProductCustomizationConstants.TRANSFORM_CSV_PATH).append("\"), properties); //$NON-NLS-1$\n");
		buf.append("//").append(IProductCustomizationConstants.PDE_AUTOGEN_END).append(" - do not delete\n");
		return buf.toString();
	}

	private static String getStopCode() {
		StringBuffer buf = new StringBuffer();
		buf.append("//").append(IProductCustomizationConstants.PDE_AUTOGEN_START).append(" - do not delete\n");
		buf.append("if (").append(REGISTRATION_NAME).append(" != null)");
		buf.append(REGISTRATION_NAME).append(".unregister();");
		buf.append("//").append(IProductCustomizationConstants.PDE_AUTOGEN_END).append(" - do not delete\n");
		return buf.toString();
	}

	private IMethod findMethod(IType type, String methodName, String packageName, String className) {
		IMethod method = type.getMethod(methodName, new String[] {Signature.createTypeSignature(packageName + "." + className, true)});
		if (method == null || !method.exists())
			method = type.getMethod(methodName, new String[] {Signature.createTypeSignature(className, false)});
		return method;
	}

	protected CoreException createCoreException(String message) {
		IStatus status = new Status(IStatus.ERROR, "org.eclipse.pde.ui", IStatus.ERROR, message, null); //$NON-NLS-1$
		return new CoreException(status);
	}
}
